;;   This file is part of scheme-GNUnet, a partial Scheme port of GNUnet.
;;   Copyright (C) 2020, 2021 Maxime Devos <maximedevos@telenet.be>
;;
;;   scheme-GNUnet is free software: you can redistribute it and/or modify it
;;   under the terms of the GNU Affero General Public License as published
;;   by the Free Software Foundation, either version 3 of the License,
;;   or (at your option) any later version.
;;
;;   scheme-GNUnet is distributed in the hope that it will be useful, but
;;   WITHOUT ANY WARRANTY; without even the implied warranty of
;;   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;;   Affero General Public License for more details.
;;
;;   You should have received a copy of the GNU Affero General Public License
;;   along with this program.  If not, see <http://www.gnu.org/licenses/>.
;;
;;   SPDX-License-Identifier: AGPL-3.0-or-later

;; Author: Maxime Devos
;; Source: gnu/gnunet/utils/hat-let.scm
;; Brief: a combination of various binding constructs
;; Status: stable, exported, pure+assert

;; Mini changelog:
;;   * (1): Original version.
;;   * (2): (2021)
;;          Mark the behaviour of <- as a historical mistake.
;;          Suggest the new <-- instead.
;;          <- might eventually be removed or be replaced
;;          with <--.
;;   * (2 1): Refer to '_' as a symbol, not as the _ from
;;            (rnrs base)
;;   * (2 2): Make (! (procedure-name argument) code code* ...)
;;            usable.
;;   * (2 3): Allow dotted variable lists with <--.

(library (gnu gnunet utils hat-let (2 2))
  (export let^)
  ;; Avoid letting users of (gnu gnunet utils hat-let)
  ;; having to import _ from (rnrs base).
  (import (only (rnrs base)
		define-syntax syntax-rules let if begin
		lambda assert call-with-values ...))

  ;; A generalisation of let*, and-let*, receive, begin,
  ;; and generalised let for avoiding nesting.
  (define-syntax let^
    (syntax-rules (? ! !! _ <- <-- /o/)
      ((: () code ...)
       (let () code ...))
      ;; if x, then return @code{(begin esc esc* ...)}
      ((: ((? x esc esc* ...) etc ...) code ...)
       (if x
	   (begin esc esc* ...)
	   (let^ (etc ...) code ...)))
      ;; Define a procedure
      ((: ((! (x . args) body ...) etc ...) code ...)
       (let ((x (lambda args body ...)))
	 (let^ (etc ...)
	       code ...)))
      ;; Bind y to x
      ((: ((! x y) etc ...) code ...)
       (let ((x y))
	 (let^ (etc ...) code ...)))
      ;; Assert it is true!
      ((: ((!! x) etc ...) code ...)
       (begin
	 (assert x)
	 (let^ (etc ...) code ...)))
      ;; Throw a result away
      ((: ((_ x) etc ...) code ...)
       (begin
	 x
	 (let^ (etc ...) code ...)))
      ;; Assign multiple values (from a thunk).
      ;; This is a historical mistake, use <--
      ;; instead (see mini changelog).
      ((: ((<- (x ...) thunk) etc ...) code ...)
       (call-with-values thunk
	 (lambda (x ...)
	   (let^ (etc ...)
		 code ...))))
      ;; Assign multiple values.
      ((: ((<-- dotted-variable-list exp) etc ...) code ...)
       (call-with-values (lambda () exp)
	 (lambda dotted-variable-list
	   (let^ (etc ...) code ...))))
      ;; Tail-call into a generalised let
      ((: ((/o/ loop (x y) ...) etc ...) code ...)
       (let loop ((x y) ...)
	 (let^ (etc ...)
	       code ...))))))
